﻿using System;
using System.Collections.Generic;
using System.IO;
using LockerRack.Drawers;
using LockerRack.Exceptions;
using LockerRack.Helpers;
using LockerRack.Interfaces;
using Microsoft.Practices.Unity;

namespace LockerRack
{
    /// <summary>
    /// This is a container for Drawers. The class knows no specifics about what drawer is,
    /// it just creates folder on disk and creates/loads Drawers
    /// </summary>
    public class Locker : IDisposable
    {
        private List<Drawer> _drawers = new List<Drawer>();
        private string _path;

        private Locker(string path, IUnityContainer unityContainer)
        {
            _path = path;
            TypesContainer = unityContainer;
        }

        public List<Drawer> Drawers
        {
            get { return _drawers; }
        }

        public string Folder
        {
            get { return _path; }
            set { _path = value; }
        }

        public IUnityContainer TypesContainer { get; private set; }

        public static Locker OpenLocker(string path, IUnityContainer unityContainer, out LockerException exception)
        {
            exception = null;
            if(!PathUtility.CheckDirectory(path))
            {
                exception = new LockerException(string.Format("Folder doesn't exist: {0}", path ?? string.Empty));
                return null;
            }

            var locker = new Locker(path, unityContainer);
            var folders = Directory.EnumerateDirectories(path);
            var exceptions = new List<Exception>();
            var preliminaryList = new List<Drawer>();
            foreach (var folder in folders)
            {
                var initParams = new Drawer.InitParams { Folder = folder, FilesFolder = folder, TypesContainer = unityContainer };

                Drawer d = Drawer.OpenDrawerPlain(initParams);
                if (initParams.Exception != null)
                {
                    exceptions.Add(initParams.Exception);
                    continue;
                }

                preliminaryList.Add(d);
            }

            BuildTree(preliminaryList, locker.Drawers);
            
            if(exceptions.Count > 0)
                exception = new LockerException(exceptions);

            return locker;
        }

        private static void BuildTree(List<Drawer> preliminaryList, List<Drawer> finalTree)
        {
            var parents = new List<Drawer>(preliminaryList.Count);
            var lostChildren = new List<Drawer>(preliminaryList.Count);
            foreach (var drawer in preliminaryList)
            {
                if (drawer.Children != null && drawer.Children.Count > 0)
                {
                    parents.Add(drawer);
                    var d = drawer;
                    var res = FindAndRemove(lostChildren, c => c.ParentId == d.Id);
                    lostChildren = res.Item2;
                    drawer.Drawers.AddRange(res.Item1);
                }
                
                if(drawer.ParentId == Guid.Empty)
                {
                    finalTree.Add(drawer);
                }
                else
                {
                    var found = parents.Find(p => p.Id == drawer.ParentId);
                    if(found != null)
                    {
                        found.Drawers.Add(drawer);
                    }
                    else
                    {
                        lostChildren.Add(drawer);
                    }
                }
            }
        }

        private static Tuple<List<Drawer>, List<Drawer>>  FindAndRemove(List<Drawer> searchList, Predicate<Drawer> searhCondition)
        {
            if (searchList == null || searchList.Count < 1)
            {
                return new Tuple<List<Drawer>, List<Drawer>>(new List<Drawer>(), searchList);
            }

            var listFound = new List<Drawer>(searchList.Count);
            var listRest = new List<Drawer>(searchList.Count);
            
            foreach(var element in searchList)
            {
                if(searhCondition(element))
                {
                    listFound.Add(element);
                }
                else
                {
                    listRest.Add(element);
                }
            }

            return new Tuple<List<Drawer>, List<Drawer>>(listFound, listRest);
        }

        public void SaveLocker(out LockerException exception)
        {
            exception = null;
            var exceptions = new List<Exception>();
            foreach (var drawer in Drawers)
            {
                drawer.Save(_path, out exception);
                if(exception != null)
                    exceptions.Add(exception);
            }

            if (exceptions.Count > 0)
                exception = new LockerException(exceptions);
        }

        public void CloseLocker()
        {
            _drawers.Clear();
        }

        public void Dispose()
        {
            TypesContainer = null;
        }

        public IDrawerFile FindFileById(Guid guid)
        {
            foreach (var drawer in Drawers)
            {
                var found = drawer.FindFileById(guid);
                if (found != null)
                    return found;
            }

            return null;
        }
    }
}